package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
)

type target struct {
	path string
}

func main() {
	puts := fmt.Println
	if len(os.Args) < 2 {
		puts("must args!")
		os.Exit(1)
	}

	targetPath := os.Args[1]
	target := &target{
		path: targetPath,
	}

	result, err := target.IsFileBeingUsed()
	if err != nil {
		puts("err: ", err)
		os.Exit(1)
	}

	puts("file was opened? -- ", result)
	os.Exit(0)
}

func (t *target) HandleProcInfo(psDir string) (bool, error) {
	psFiles, err := ioutil.ReadDir(psDir)
	if err != nil && os.IsPermission(err) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	for _, psFi := range psFiles {
		// not symbolic link, continue
		if psFi.Mode()&os.ModeSymlink == 0 {
			continue
		}
		psFilePath := filepath.Join(psDir, psFi.Name())
		targetFilePath, err := os.Readlink(psFilePath)
		// skip file is not exist and permission denied
		if err != nil && !os.IsNotExist(err) && !os.IsPermission(err) {
			return false, err
		}
		if targetFilePath == t.path {
			return true, nil
		}
	}
	return false, nil
}

func (t *target) GatherProcInfo() (bool, error) {
	// traverse directory /proc
	procDir := "/proc"
	pdFiles, err := ioutil.ReadDir(procDir)
	if err != nil {
		return false, err
	}
	for _, pdFi := range pdFiles {
		// only need dir
		if !pdFi.IsDir() {
			continue
		}

		pdFileName := pdFi.Name()
		// only need dir name is number
		if _, err := strconv.Atoi(pdFileName); err != nil {
			continue
		}

		// traverse directory /proc/xxx/fd
		// 'xxx' is number(pid)
		psDir := filepath.Join(procDir, pdFileName, "fd")
		r, err := t.HandleProcInfo(psDir)
		if err != nil || r {
			return r, err
		}
	}
	return false, nil
}

func (t *target) IsFileBeingUsed() (bool, error) {
	return t.GatherProcInfo()
}
